浅谈各种连续变量分箱原理以及内涵(卡方,KS,IV值,树模型) 您所在的位置:网站首页 best ks 浅谈各种连续变量分箱原理以及内涵(卡方,KS,IV值,树模型)

浅谈各种连续变量分箱原理以及内涵(卡方,KS,IV值,树模型)

2024-06-21 00:08| 来源: 网络整理| 查看: 265

文章目录 1.变量分箱对模型的好处 1.降低异常值的影响,增加模型的稳定性 2.缺失值作为特殊变量参与分箱,减少缺失值填补的不确定性(分箱还可以解决缺失值 ) 3.增加变量的可解释性 4.增加变量的非线性 5.增加模型的预测效果 2.分箱的局限 1.同一箱内的样本具有同质性 2.需要专家经验支持 3.变量分箱要注意的问题 1.分箱结果不宜过多 2.分箱结果不宜过少 3.分箱后单调性的要求 4.变量分箱的流程 变量分箱的流程 5.卡方分箱 6.KS分箱 7.混淆矩阵概念复习 8.最优IV分箱 9.基于树的最优分箱方法 10.分箱框架源码(卡方、最优IV、信息增益)

什么是变量分箱:   变量分箱(特征分箱)是一种特征工程方法. 变量分箱的目的:   意在增强变量的可解释性与预测能力。

对于变量取值较稀疏的离散变量也应该进行分箱处理。

比如借款人的地址信息往往非常稀疏,通常先对地址信息处理到省或市,用每个省或市的坏样本比率进行数值化,将数值化后的变量作为连续变量进行分箱.

1.变量分箱对模型的好处

1.降低异常值的影响,增加模型的稳定性 1 - 通过分箱 来降低噪声,使模型鲁棒性更好 2.缺失值作为特殊变量参与分箱,减少缺失值填补的不确定性(分箱还可以解决缺失值 ) 123- 通常的做法是,离散特征将缺失值转为字符串作为特殊字符即可, - 连续特征将缺失值作为特殊值即可  - 在后面的代码中连续值填充-777,离散值填充NA 3.增加变量的可解释性 1 - 分箱的方法往往要配合变量编码使用,这就大大提高了变量的可解释性 4.增加变量的非线性 1- 提高了模型的拟合能力

5.增加模型的预测效果 12- 通常假设训练集和测试集满足同分布,分箱使连续变量离散化,更容易满足同分布的假设 - 即减少模型在训练集的表现和测试集的偏差

2.分箱的局限

1.同一箱内的样本具有同质性 123456    分箱的基本假设是分在一个箱内的样本(借款人)具有相同     的风险等级,比如按年龄分箱的结果为{[18,25],[25,40],     [40,55],[55,100]},也就是将年龄在 18~25 的借款人统一按照     同一个数值变量来代替。对于树模型就减少了模型选择最优切分     点的可选择范围,会对模型的预测能力产生影响,损失了模型的     分辨能力 2.需要专家经验支持

3.变量分箱要注意的问题

分箱分的不好的话有些值的预测能力会忽略 会影响模型的预测能力 削弱模型的预测能力

1.分箱结果不宜过多 分箱过多导致特征过于稀疏,编码后的特征维度快速增加,使特征更加稀疏,会降低模型的预测效果 极端例子 一共100个样本 分了100个箱子 严重失衡 2.分箱结果不宜过少 每个箱子默认是同质的即风险等级相同 如果分箱过少则可能会造成模型辨识度过低 例如,年龄分箱结果为{[18,50],[50,100]},认为18~50岁的借款人风险水平相同这是不符合业务解释的。 3.分箱后单调性的要求

4.变量分箱的流程

变量分箱的目的是增加变量的预测能力或减少变量的自身冗余。

当预测能力不再提升或冗余性不再降低时,则分箱完毕。

因此,分箱过程是一个优化过程,所有满足上述要求的指标都可以用于变量分箱,这个指标也可叫作目标函数,可以终止或改变分箱的限制就是优化过程的约束条件。

变量分箱的流程

5.卡方分箱

基本思想:自底向上的分箱方法,相邻区间合并计算卡方值,卡方值越小说明两个区间的类分布越相似,合并两个区间

由设定的阈值决定(自由度、置信度),小于阈值就分箱

自底向上:由多至少逐层合并的过程 自顶向下是由少至多逐层切分的过程

12345数值特征离散化 特征之间强相关不好,但是某个特征和标签相关是好的 强相关:一个特征可以用另一个特征线性表示

解释性强 能解决多分类场景的分箱 缺点是计算量大 需要先对数值型变量离散化,然后迭代的计算卡方值

公式: 上述过程就是一个卡方检验的过程,因此,根据置信度和自由度可以计算出卡方检验的阈值,当计算的卡方值小于阈值,则认为相邻区间的类分布情况相似,可进行合并。其中自由度为类别个数减 1,即本例中的自由度为 1;置信度可以使用 0.9、0.95和 0.99。

6.KS分箱

Best-KS 分箱方法是一种自顶向下的分箱方法。与卡方分箱相比,Best-KS分箱方法只是目标函数采用了 KS 统计量,其余分箱步骤没有差别 注意:KS只能处理连续变量 可以用于模型对好坏样本的区分能力

基本思想:   根据KS曲线,取TPR和FPR之间的最大差值,就是KS统计率,也就是KS分箱最优切分点的位置.    KS曲线:

横轴就是认为设定的阈值,就是区分好坏样本的界限 纵轴:一个是真正率TPR,一个是假正率FPR 之间的差值一定程度反映模型对好坏样本的区分能力 我们希望真正率高一点,假正率低一点(好样本多一点,坏样本少一点) 真正率:正样本预测数 / 正样本实际数 TP /(TP + FN) 假正率:被预测为正的负样本结果数 / 负样本实际数 FP /(FP + TN)

KS分箱过程也就是递归的找最优切分点的过程

KS值越大 模型的区分能力越强

7.混淆矩阵概念复习

召回率,真正率(recall):TP/(TP+FN) 准确率(accuracy):(TP+TN) / (TP+TN+FP+FN) 预测正确的 / 总样本数 精确率(precision):TP / (TP+FP) 预测为1且正确 / 所有预测为1的样本数

8.最优IV分箱

最优 IV 分箱方法也是自顶向下的分箱方式,其目标函数为 IV 值

IV 值其本质是对称化的 K-L 距离,即在切分点处分裂得到的两部分数据中,选择好坏样本的分布差异最大点作为最优切分点。分箱结束后,计算每个箱内的 IV 值加和得到变量的 IV 值,可以用来刻画变量对目标值的预测能力。即变量的 IV 值越大,则对目标变量的区分能力越强,因此,IV 值还可以用来做变量选择

9.基于树的最优分箱方法

基于树的分箱方法借鉴了决策树 在树生成的过程中特征选择(最优分裂点) 的目标函数来完成变量分箱过程,可以理解为单变量的决策树模型。决策树采用自顶向下递归的方法进行树的生成,每个节点的选择目标是为了分类结果的纯度更高,也就是样本的分类效果更好。因此,不同的损失函数有不同的决策树,ID3采用信息增益方法,C4.5 采用信息增益比,CART 采用基尼系数(Gini)指标。

10.分箱框架源码(卡方、最优IV、信息增益)

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348# -*- coding: utf-8 -*- import os import pandas as pd import numpy as np import pickle from sklearn.preprocessing import OneHotEncoder from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import train_test_split import warnings warnings.filterwarnings("ignore")  ##忽略警告 # 注意sklearn版本要在v.20.0以上,不同版本函数的位置会不同。 def data_read(data_path, file_name):     df = pd.read_csv(os.path.join(data_path, file_name), delim_whitespace=True, header=None, engine='python')     # 变量重命名     columns = ['status_account', 'duration', 'credit_history', 'purpose', 'amount',                'svaing_account', 'present_emp', 'income_rate', 'personal_status',                'other_debtors', 'residence_info', 'property', 'age',                'inst_plans', 'housing', 'num_credits',                'job', 'dependents', 'telephone', 'foreign_worker', 'target']     df.columns = columns     # 将标签变量由状态1,2转为0,1;0表示好用户,1表示坏用户     df.target = df.target - 1     # 数据分为data_train和 data_test两部分,训练集用于得到编码函数,验证集用已知的编码规则对验证集编码     # x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)     # stratify: 依据标签y,按原数据y中各类比例,分配给train和test,使得train和test中各类数据的比例与原数据集一样     data_train, data_test = train_test_split(df, test_size=0.2, random_state=0, stratify=df.target)     return data_train, data_test # one—hot编码 # df: 数据框  data_path_1:编码模型保存的位置  flag:数据集 def onehot_encode(df, data_path_1, flag='train'):     # reset_index:重置索引, drop=True:不想保留原来的index     df = df.reset_index(drop=True)     # 判断数据集是否存在缺失值  如果是进行缺失值填补     # df.isnull().any() 判断哪些列存在缺失值     if sum(df.isnull().any()) > 0:         # 数值型和字符串型特征拿出来         numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']         var_numerics = df.select_dtypes(include=numerics).columns         var_str = [i for i in df.columns if i not in var_numerics]         # 数据类型的缺失值用-77777填补         if len(var_numerics) > 0:             df.loc[:, var_numerics] = df[var_numerics].fillna(-7777)         # 字符串类型的缺失值用NA填补         if len(var_str) > 0:             df.loc[:, var_str] = df[var_str].fillna('NA')     # pickle.dump(obj, file, [,protocol])  序列化对象,将对象obj保存到文件file中去     # 参数protocol是序列化模式,默认是0(ASCII协议,表示以文本的形式进行序列化)     if flag == 'train':         enc = OneHotEncoder(dtype='int').fit(df)         # 保存编码模型         with open(os.path.join(data_path_1, 'onehot.pkl'), 'wb') as save_model:             pickle.dump(enc, save_model, 0)         df_return = pd.DataFrame(enc.transform(df).toarray())         df_return.columns = enc.get_feature_names(df.columns)     elif flag == 'test':         # 测试数据编码         with open(os.path.join(data_path_1, 'onehot.pkl'), 'rb') as read_model:             onehot_model = pickle.load(read_model)         # 如果训练集无缺失值,测试集有缺失值则将该样本删除         var_range = onehot_model.categories_  # 训练集one-hot编码后的类别种类         var_name = df.columns         del_index = []         for i in range(len(var_range)):             if 'NA' not in var_range[i] and 'NA' in df[var_name[i]].unique():                 index = np.where(df[var_name[i]] == 'NA')                 del_index.append(index)             elif -7777 not in var_range[i] and -7777 in df[var_name[i]].unique():                 index = np.where(df[var_name[i]] == -7777)                 del_index.append(index)         # 删除样本         if len(del_index) > 0:             del_index = np.unique(del_index)             df = df.drop(del_index)             print('训练集无缺失值,但测试集有缺失值,第{0}条样本被删除'.format(del_index))         df_return = pd.DataFrame(onehot_model.transform(df).toarray())         df_return.columns = onehot_model.get_feature_names(df.columns)     elif flag == 'transform':         # 编码数据值转化为原始变量         with open(os.path.join(data_path_1, 'onehot.pkl'), 'rb') as read_model:             onehot_model = pickle.load(read_model)         # 逆变换         df_return = pd.DataFrame(onehot_model.inverse_transform(df))         df_return.columns = np.unique(['_'.join(i.rsplit('_')[:-1]) for i in df.columns])     return df_return # 标签编码 def label_encode(df, data_path_1, flag='train'):     if flag == 'train':         enc = LabelEncoder().fit(df)         # 保存编码模型         with open(os.path.join(data_path_1, 'labelcode.pkl'), 'wb') as save_model:             pickle.dump(enc, save_model, 0)         df_return = pd.DataFrame(enc.transform(df))         df_return.name = df.name     elif flag == 'test':         # 测试数据编码         with open(os.path.join(data_path_1, 'labelcode.pkl'), 'rb') as read_model:             label_model = pickle.load(read_model)         df_return = pd.DataFrame(label_model.transform(df))         df_return.name = df.name     elif flag == 'transform':         # 编码数据值转化为原始变量         with open(os.path.join(data_path_1, 'labelcode.pkl'), 'rb') as read_model:             label_model = pickle.load(read_model)         # 逆变换         df_return = pd.DataFrame(label_model.inverse_transform(df))     return df_return # 自定义映射 def dict_encode(df, data_path_1):     # 自定义映射     embarked_mapping = {}     embarked_mapping['status_account'] = {'NA': 1, 'A14': 2, 'A11': 3, 'A12': 4, 'A13': 5}     embarked_mapping['svaing_account'] = {'NA': 1, 'A65': 1, 'A61': 3, 'A62': 5, 'A63': 6, 'A64': 8}     embarked_mapping['present_emp'] = {'NA': 1, 'A71': 2, 'A72': 5, 'A73': 6, 'A74': 8, 'A75': 10}     embarked_mapping['property'] = {'NA': 1, 'A124': 1, 'A123': 4, 'A122': 6, 'A121': 9}     df = df.reset_index(drop=True)     # 判断数据集是否存在缺失值     if sum(df.isnull().any()) > 0:         df = df.fillna('NA')     # 字典映射     var_dictEncode = []     for i in df.columns:         col = i + '_dictEncode'         df[col] = df[i].map(embarked_mapping[i])         var_dictEncode.append(col)     return df[var_dictEncode] # WOE编码 # 返回某个特征的woe映射后的df、woe字典、iv值 # x:特征   y:类别   target:正样本为1 def woe_cal_trans(x, y, target=1):     # 计算总体的正负样本数     p_total = sum(y == target)  # 正样本数     n_total = len(x) - p_total  # 负样本数     value_num = list(x.unique())  # 去重后的总数     woe_map = {}     iv_value = 0     for i in value_num:  # 这个特征每种取值的woe值         # 计算该变量取值箱内的正负样本总数         y1 = y[np.where(x == i)[0]]         p_num_1 = sum(y1 == target)         n_num_1 = len(y1) - p_num_1         # 计算占比         # bad_1 = p_num_1 / p_total  # 坏样本分布率         # good_1 = n_num_1 / n_total  # 好样本分布率         good_1 = p_num_1 / p_total  # 坏样本分布率         bad_1 = n_num_1 / n_total  # 好样本分布率         if bad_1 == 0:             bad_1 = 1e-5         elif good_1 == 0:             good_1 = 1e-5         woe_map[i] = np.log(bad_1 / good_1)  # woe值         iv_value += (bad_1 - good_1) * woe_map[i]  # iv值     x_woe_trans = x.map(woe_map)     x_woe_trans.name = x.name + "_woe"     return x_woe_trans, woe_map, iv_value # WOE编码映射 def woe_encode(df, data_path_1, varnames, y, filename, flag='train'):     """     Param:     df: 待编码数据     data_path_1 :存取文件路径     varnames: 变量列表     y:  目标变量     filename:编码存取的文件名     flag: 选择训练还是测试     ---------------------------------------     Return:     df: 编码后的数据,包含了原始数据     woe_maps: 编码字典     iv_values: 每个变量的IV值     var_woe_name: 每个特征拼接woe的列名     """     df = df.reset_index(drop=True)  # 重置索引,不保留原来的索引     # 判断数据集是否存在缺失值     if sum(df.isnull().any()) > 0:         numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']         var_numerics = df.select_dtypes(include=numerics).columns  # 数值型特征         var_str = [i for i in df.columns if i not in var_numerics]  # 字符串型特征         # 数据类型的缺失值用-77777填补         if len(var_numerics) > 0:             df.loc[:, var_numerics] = df[var_numerics].fillna(-7777)         # 字符串类型的缺失值用NA填补         if len(var_str) > 0:             df.loc[:, var_str] = df[var_str].fillna('NA')     if flag == 'train':         iv_values = {}  # 保存每个特征的iv值         woe_maps = {}  # 保存每个特征的woe值         var_woe_name = []         for var in varnames:  # 遍历每一个特征             x = df[var]             # 变量映射             x_woe_trans, woe_map, info_value = woe_cal_trans(x, y)             var_woe_name.append(x_woe_trans.name)             df = pd.concat([df, x_woe_trans], axis=1)  # 按行拼接woe值             woe_maps[var] = woe_map             iv_values[var] = info_value         # 保存woe映射字典         with open(os.path.join(data_path_1, filename + '.pkl'), 'wb') as save_woe_dict:             pickle.dump(woe_maps, save_woe_dict, 0)         return df, woe_maps, iv_values, var_woe_name     elif flag == 'test':         # 测试数据编码         with open(os.path.join(data_path_1, filename + '.pkl'), 'rb') as read_woe_dict:             woe_dict = pickle.load(read_woe_dict)         # 如果训练集无缺失值,测试集有缺失值则将该样本删除         woe_dict.keys()         del_index = []         for key, value in woe_dict.items():             if 'NA' not in value.keys() and 'NA' in df[key].unique():                 index = np.where(df[key] == 'NA')                 del_index.append(index)             elif -7777 not in value.keys() and -7777 in df[key].unique():                 index = np.where(df[key] == -7777)                 del_index.append(index)         # 删除样本         if len(del_index) > 0:             del_index = np.unique(del_index)             df = df.drop(del_index)             print('训练集无缺失值,但测试集有缺失值,该样本{0}删除'.format(del_index))         # WOE编码映射         var_woe_name = []         for key, value in woe_dict.items():             val_name = key + "_woe"             df[val_name] = df[key].map(value)             var_woe_name.append(val_name)         return df, var_woe_name if __name__ == '__main__':     path = r'G:\A_实训前置1\python_workspace\finance_code\chapter5\'     data_path = os.path.join(path, 'data')     file_name = 'german.csv'     # 读取数据     data_train, data_test = data_read(data_path, file_name)     # 不可排序变量     var_no_order = ['credit_history', 'purpose', 'personal_status', 'other_debtors',                     'inst_plans', 'housing', 'job', 'telephone', 'foreign_worker']     # x_woe_trans, woe_map, iv_value = woe_cal_trans(data_train['job'], data_test['target'])     # print(x_woe_trans)     # print(woe_map)     # print(iv_value)     # one-hot编码     # 训练数据编码     data_train.credit_history[882] = np.nan     data_train_encode = onehot_encode(data_train[var_no_order], data_path, flag='train')     # 测试集数据编码     data_test.credit_history[529] = np.nan     data_test.purpose[355] = np.nan     data_test_encode = onehot_encode(data_test[var_no_order], data_path, flag='test')     # 查看编码逆变化后的原始变量名     df_encoded = data_test_encode.loc[0:4]     data_inverse = onehot_encode(df_encoded, data_path, flag='transform')     print(data_inverse)     # 哑变量编码     data_train_dummies = pd.get_dummies(data_train[var_no_order])     data_test_dummies = pd.get_dummies(data_test[var_no_order])     print(data_train_dummies.columns)     # 可排序变量     # 注意,如果分类变量的标签为字符串,这是需要将字符串数值化才可以进行模型训练,标签编码其本质是为     # 标签变量数值化而提出的方法,因此,其值支持单列数据的转化操作,并且转化后的结果是无序的。     # 因此有序变量统一用字典映射的方式完成。     var_order = ['status_account', 'svaing_account', 'present_emp', 'property']     # 标签编码     # 训练数据编码     data_train_encode = label_encode(data_train[var_order[1]], data_path, flag='train')     # 验证集数据编码     data_test_encode = label_encode(data_test[var_order[1]], data_path, flag='test')     # 查看编码你变化后的原始变量名     # 后面再改一下     df_encoded = data_test_encode     data_inverse = label_encode(df_encoded, data_path, flag='transform')     # 自定义映射     # 训练数据编码     data_train.credit_history[882] = np.nan     data_train_encode = dict_encode(data_train[var_order], data_path)     # 测试集数据编码     data_test.status_account[529] = np.nan     data_test_encode = dict_encode(data_test[var_order], data_path)     print(data_test_encode)     # WOE编码     # 训练集WOE编码     df_train_woe, dict_woe_map, dict_iv_values, var_woe_name = woe_encode(data_train, data_path, var_no_order,                                                                           data_train.target, 'dict_woe_map',                                                                           flag='train')     print(df_train_woe, '\n')     print(dict_woe_map, '\n')     print(dict_iv_values, '\n')     print(var_woe_name, '\n')     # 测试集WOE编码     df_test_woe, var_woe_name = woe_encode(data_test, data_path, var_no_order, data_train.target, 'dict_woe_map',                                            flag='test')     print(df_train_woe)



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有